In typical event driven windowing systems, such as Microsoft Windows, events trigger the passing of messages between windows and between a window and the windowing system. Every window that a program creates has an associated window procedure. This window procedure is a function that could be either in the program itself or in a dynamic link library. The window procedure processes messages sent to the window. Very often these messages inform a window of user input from the keyboard or mouse. Other messages tell a window when it is being resized or when the surface of the window needs to be repainted.
When the windowing system detects an event directed to a certain window, the windowing system routes an appropriate message to that window by calling the window's window procedure. The window procedure does some processing based on the message and then returns control to the windowing system.
A program that uses a window must register the window with the windowing system. When registering a window, the program specifies the window procedure that is to receive messages for the window by specifying a window class. A window class identifies the window procedure associated with a window. The use of a window class allows multiple windows to be based on the same window class and hence use the same window procedure. For example, in Microsoft Windows all button windows are typically created based on the same window class. Only the first instance of the program needs to register the window class. After the window class is registered, the window class then becomes available to all subsequent instances of the program. When a program registers a window, the programmer defines additional characteristics of the window that are unique to that window. A window class can also be registered in a dynamic link library so the window class is available to all programs.
After a program's window is created and displayed, the program makes itself ready to accept input from the user. When an input event occurs, the windowing system translates the event into a message and invokes the window procedure for the appropriate window.
The code in Table A is an example of a window procedure for a window that displays the message "Hello, World!"
TABLE A ______________________________________ LONG WndProc (HWND hwnd, UINT message, UINT wParam, LONG lParam) switch (message) { case WM.sub.-- PAINT: [Instructions for painting "Hello, World!" in a window] . . . return 0; case WM.sub.-- COMMAND: switch (wParam) { case IDM.sub.-- ABOUT: [instructions for processing About command] . . . return 0; case IDM.sub.-- EXIT [instructions for processing EXIT command] . . . return 0; . . . } return 0; case WM.sub.-- DESTROY: [instructions for destroying window] . . . return 0; case WM.sub.-- SIZE: [instructions for sizing window] . . . return 0; . . . } return DefWindowProc (hwnd, message, wParam, lParam); } ______________________________________
The first parameter of the window procedure WndProc is hwnd, the handle to the window receiving the message. If a program creates multiple windows based on the same window class (and hence the same window procedure), then hwnd identifies the particular window receiving the message. The second parameter, message, is a 16-bit unsigned integer that identifies the message. The last two parameters, wParam and lParam, provide more information about the message.
The switch and case construction is used in the window procedure to determine what message the window procedure is receiving and how to process the message accordingly. The window procedure contains a case statement for every message the window procedure will process. Because the message parameter in this example is a 16-bit unsigned integer, message can identify 65,536 (2.sup.16) unique messages, which requires a rather unwieldy switch statement. Additionally, the switch statement can be multi-dimensional, i.e., a switch statement within a switch statement, as shown in regard to the processing of WM.sub.-- COMMAND messages.
As an example of a call to WndProc, assume a user wants to resize a window, MyWindow, associated with WndProc. The user provides input to the windowing system, which translates the input into a message and then calls WndProc. The hwnd parameter is the handle to MyWindow; the message parameter is WM.sub.-- SIZE; the wParam parameter contains a number from 0 through 4 that decodes to provide additional information on why the message was sent; and the lParam parameter contains the new size of the window. The meaning of wParam and lParam depends on the message type. These message parameters can be pointers to structures or they can be composites of flags and fields. All messages not processed in the window procedure are usually passed to a default function, DefWindowProc.
An event driven system such as Microsoft Windows can be implemented using object-oriented programming techniques. Object-oriented programming techniques employ a concept referred to as inheritance to allow the sharing of code. To understand the concept of inheritance, it is helpful to understand object-oriented programming techniques generally. Two common characteristics of object-oriented programming languages are support for data encapsulation and data type inheritance. Data encapsulation refers to the binding of functions and data. Inheritance refers to the ability to declare a data type in terms of other data types.
In the C++ programming language, object-oriented techniques are supported through the use of classes. A class is a user-defined type. A class declaration describes the data members and member functions of the class. For example, the following instructions define data members and a member function of a class named CIRCLE.
______________________________________ class CIRCLE { public: int x, y; int radius; void draw( ); }; ______________________________________ Variables x and y specify the center location of a circle and variable radius specifies the radius of the circle. These variables are referred to as data members of the class CIRCLE. The function draw is a user-defined function that draws the circle of the specified radius at the specified location. The function draw is referred to as a member function of class CIRCLE. The data members and member functions of a class are bound together in that the function operates on an object of the class. An instance of a class is also called an instance of the class.
In the syntax of C++, the following statement defines the objects a and b to be of type class CIRCLE.
CIRCLE a, b; PA1 a.draw(); PA1 b.draw(); PA1 CIRCLE *c.sub.-- ptr; PA1 c.sub.-- ptr=&c; PA1 CIRCLE a; PA1 CIRCLE.sub.-- FILL b; PA1 a.draw(); PA1 b.draw();
This definition causes the allocation of memory for the objects a and b. The following statements assign data to the data members of objects a and b.
______________________________________ a.x = 2; a.y = 2; a.radius = 1; b.x = 4; b.y = 5; b.radius = 2; ______________________________________
The following statements are used to draw the circles defined by objects a and b.
A derived class is a class that inherits the characteristics-data members and member functions--of its base classes. For example, the following derived class CIRCLE.sub.-- FILL inherits the characteristics of the base class CIRCLE.
______________________________________ class CIRCLE.sub.-- FILL : CIRCLE { public: int pattern; void fill( ); }; ______________________________________
This class definition specifies that class CIRCLE.sub.-- FILL includes all the data and member functions that are in class CIRCLE in addition to those data and member functions introduced in the declaration of class CIRCLE.sub.-- FILL, that is, data member pattern and member function fill. In this example, class CIRCLE.sub.-- FILL has data members x, y, radius, and pattern and member functions draw and fill. Class CIRCLE.sub.-- FILL is said to "inherit" the characteristics of class CIRCLE.
A class that inherits the characteristics of another class is a derived class (e.g., CIRCLE.sub.-- FILL). A class whose characteristics are inherited by another class is a base class (e.g., CIRCLE is a base class of CIRCLE.sub.-- FILL). A class can be both a base class and a derived class. A derived class may inherit the characteristics of several classes, that is, a derived class may have several base classes. This is referred to as multiple inheritance.
A class may also specify whether its member functions are virtual. Declaring that a member function is virtual means that the function can be overridden by a function of the same name and type in a derived class. The ability to redefine a member function in a derived class is known as polymorphism. Polymorphism allows a programmer to define a base class that includes routines that perform standard operations on groups of related objects without regard to the type of each object. The programmer can redefine member functions in derived classes, taking into account the type of the object. In the following example, the function draw is declared to be virtual in classes CIRCLE and CIRCLE.sub.-- FILL.
______________________________________ class CIRCLE { public: int x, y; int radius; virtual void draw( ); }; class CIRCLE.sub.-- FILL : CIRCLE { public: int pattern; virtual void draw( ); }; ______________________________________
The C++ language provides a pointer data type. A pointer holds values that are addresses of objects in memory. Through a pointer, an object can be referenced. The following statement declares variable c.sub.-- ptr to be a pointer on an object of type class CIRCLE and sets variable c.sub.-- ptr to hold the address of object c.
Continuing with the same example, the following statement declares object a to be of type class CIRCLE and object b to be of type class CIRCLE.sub.-- FILL.
The following statement refers to the function draw as defined in class CIRCLE.
Whereas, the following statement refers to the function draw defined in class CIRCLE.sub.-- FILL.
Moreover, the following statements type cast object b to an object of type class CIRCLE and invoke the function draw that is defined in class CIRCLE.sub.-- FILL.
______________________________________ CIRCLE *c.sub.-- ptr; c.sub.-- ptr = &b; c.sub.-- ptr.fwdarw.draw( ); // CIRCLE.sub.-- FILL::draw( ) ______________________________________
Thus, the virtual member function that is called is function CIRCLE.sub.-- FILL::draw ().
FIG. 1 is a block diagram illustrating typical data structures used to represent an object. An object is composed of data members and member functions, which implement the behavior of the object. The data structures used to represent an object comprise object data structure 101, virtual member function table 102, and member functions 103, 104, 105. The object data structure 101 contains data members and a pointer to the virtual function table 102. The virtual function table 102 contains an entry for each virtual member function defined for the object. Each entry contains a reference to the code that implements the corresponding member function.
When a class introduces a virtual member function, then the compiler allocates a virtual function table for the class. The class data structure contains the layout of the data members and internal pointers. The internal pointers are virtual function table pointers. These pointers are initialized during run time to point to the virtual function table associated with the class. During compile time, the compiler initializes the virtual function tables with addresses corresponding to virtual member functions.
A virtual function table for a class contains addresses corresponding to the virtual member functions associated with that class. The virtual function table is used at run time to invoke the virtual member functions indirectly. Each class with a virtual member function has an associated virtual function table. The virtual function table contains the address of each virtual member function in the class in order of declaration. The data structure for such a class contains a virtual function table pointer (pVTable). When memory for a data structure is allocated at run time, the virtual function table pointer is initialized with the address of the associated virtual function table. Thus, all objects of a certain class type point to the same virtual function table. To implement the invoking of a virtual member function, the compiler generates code to access the virtual member function through the virtual function table.
A derived class inherits a copy of the virtual function tables associated with its direct base classes. Also, a derived class that introduces a virtual member function either has an additional virtual function table or shares one of its inherited virtual function tables. A derived class is said to "introduce" a virtual member function if there are no other virtual member functions of the same name and type in any of its direct base classes. A derived class shares an inherited virtual function table by appending the entries for the member functions introduced in the derived class to the inherited table.
FIG. 2 illustrates sample data structures of an object 201 associated with a class A, an object 207 associated with a class B derived from class A, and an object 209 associated with a class C derived from class B. A virtual function table 202 associated with the object 201 of class A contains pointers to member functions 203, 204, 205.
The object 207 of class B inherits the member functions 203, 205 of class A, and overrides the member function 204 with a member function 206. A virtual function table 208 associated with object 207 of class B contains pointers to the member functions 203, 205, 206. The object 209 of class C inherits the member functions 203, 205, 206 of class B. A virtual function table 210 associated with the object 209 of class C contains the same pointers as the virtual function table 208 associated with the object 207 of class B.
Storing multiple copies of a virtual function table for each derived class of a base function can use up substantial amounts of memory. For example, in the Microsoft Windows Foundation Classes Version 1.0, the class CWnd is comprised of approximately two hundred member functions. Because the class CFrameWnd is derived from the class CWnd, the derived class CFrameWnd is comprised of approximately two hundred inherited member functions plus six introduced member functions. A complete listing of the data members and member functions of each of these classes may be found in the Microsoft C/C++ Class Libraries Reference (1991), which is hereby incorporated by reference.